﻿using System;
using System.IO;
using System.Threading;
using hundsun.mcapi;
using hundsun.t2sdk;
using Skyline;
using ufxfutucollectionforcs;

namespace QuantBox.XApi
{
    public sealed class UfxConnection
    {
        private string _settingFile = string.Empty;
        private string _settingPath = string.Empty;
        private CT2Connection? _ufxConnection;
        private CT2Connection? _mcConnection;
        private CT2SubscribeInterface? _subscriber;
        private LongRunningTask? _task;
        private int _timeout;
        private string _userToken = string.Empty;

        private string GetAbsolutePath(string path)
        {
            return path.Replace("`pwd`", _settingPath);
        }

        private CT2Configinterface GetSubscribeConfig()
        {
            var iniPath = Path.Combine(_settingPath, "subscriber.ini");
            return GetSdkConfig(iniPath);
        }

        private CT2Configinterface GetSdkConfig(string iniFile)
        {
            var ufxConfig = new CT2Configinterface();
            ufxConfig.Load(iniFile);

            var licenseFile = ufxConfig.GetString("t2sdk", "license_file", string.Empty);
            if (!string.IsNullOrEmpty(licenseFile) && !File.Exists(licenseFile)) {
                ufxConfig.SetString("t2sdk", "license_file", GetAbsolutePath(licenseFile));
            }

            var certFile = ufxConfig.GetString("safe", "cert_file", string.Empty);
            if (!string.IsNullOrEmpty(certFile) && !File.Exists(certFile)) {
                ufxConfig.SetString("safe", "cert_file", GetAbsolutePath(certFile));
            }

            return ufxConfig;
        }

        private void Run(CancellationToken ct)
        {
            LogHappened?.Invoke("开始连接UFX服务器......");
            var ufxConfig = GetSdkConfig(_settingFile);
            _timeout = ufxConfig.GetInt("t2sdk", "timeout", 5000);
            _ufxConnection = new CT2Connection(ufxConfig);
            {
                ConnectionCreated?.Invoke(_ufxConnection);
                var ret = _ufxConnection.Connect((uint)_timeout);
                if (ret != 0) {
                    ErrorHappened?.Invoke(new UfxErrorInfo(ret, $"UFX服务器连接失败:{_ufxConnection.GetErrorMsg(ret)}"));
                    return;
                }
                Connected?.Invoke(_ufxConnection);
            }
            LogHappened?.Invoke("开始连接订阅服务器......");
            var mcConfig = GetSubscribeConfig();
            _mcConnection = new CT2Connection(mcConfig);
            {
                SubscriberCreated?.Invoke(_mcConnection);
                var ret = _mcConnection.Connect((uint)_timeout);
                if (ret != 0) {
                    ErrorHappened?.Invoke(new UfxErrorInfo(ret, $"订阅服务器连接失败:{_mcConnection.GetErrorMsg(ret)}"));
                    return;
                }
                LogHappened?.Invoke(nameof(SubscriberConnected));
                SubscriberConnected?.Invoke(_mcConnection);
            }

            if (!string.IsNullOrEmpty(_userToken)) {
                while (!ct.IsCancellationRequested) {
                    Utility.Sleep(10 * 1000, ct);
                    AsyncCall(UfxAccountFunction.Make10000(_userToken));
                }
            }
            else {
                ErrorHappened?.Invoke(new UfxErrorInfo(-1, "未完成用户登陆!"));
            }

            _subscriber?.Dispose();
            _ufxConnection?.Close();
            _mcConnection?.Close();
        }

        public event Action<CT2Connection>? ConnectionCreated;
        public event Action<CT2Connection>? SubscriberCreated;
        public event Action<CT2Connection>? Connected;
        public event Action<CT2Connection>? SubscriberConnected;
        public event Action<UfxErrorInfo>? ErrorHappened;
        public event Action<string>? LogHappened;

        public UfxErrorInfo Login(string operatorNo, string password, string authorizationId)
        {
            var function = UfxAccountFunction.Make10001(
                operatorNo,
                password,
                _ufxConnection!.GetSelfMac(),
                _ufxConnection.GetSelfAddress(),
                string.Empty,
                authorizationId);

            var (result, error) = SyncCall(function);
            if (error == UfxErrorInfo.Ok) {
                _userToken = Ufx10001Result.ReadOne(result).UserToken;
            }
            return error;
        }

        public UfxErrorInfo FuturesLogin(string operatorNo, string password, string? hdVolSerial = null)
        {
            var address = _ufxConnection!.GetSelfAddress().Split(':');
            var mac = _ufxConnection.GetSelfMac();
            var config = GetClientConfig();
            var term = new UFXTermInfo();
            var userToken = string.Empty;
            hdVolSerial ??= config.GetString("client", "hd_vol_serial", string.Empty);
            var ret = term.packt2futucollectioninfoforcs(
                _ufxConnection,
                operatorNo,
                password,
                config.GetString("client", "appid", string.Empty),
                config.GetString("client", "authorize_code", string.Empty),
                config.GetString("client", "login_type", string.Empty),
                config.GetString("client", "securities_branch", string.Empty),
                config.GetString("client", "authorization_id", string.Empty),
                config.GetString("client", "verification_code", string.Empty),
                address[0],
                address[1],
                mac,
                hdVolSerial,
                $"IP.{address[0]} MAC.{mac} HD.{hdVolSerial}",
                config.GetString("client", "appid", string.Empty),
                ref userToken);
            config.Dispose();
            if (ret == 0) {
                _userToken = userToken;
            }
            return ret == 0 ? UfxErrorInfo.Ok : new UfxErrorInfo(ret, userToken);
        }

        private CT2Configinterface GetClientConfig()
        {
            var config = new CT2Configinterface();
            config.Load(Path.Combine(_settingPath, "ufxclient.ini"));
            return config;
        }

        public UfxErrorInfo AsyncCall(int functionId, CT2Packer packer)
        {
            if (string.IsNullOrEmpty(_userToken)) {
                return new UfxErrorInfo(-1, "UFX 未连接");
            }

            using var msg = new CT2BizMessage();
            unsafe {
                msg.SetFunction(functionId);
                msg.SetPacketType(0);
                msg.SetContent(packer.GetPackBuf(), packer.GetPackLen());
                var ret = _ufxConnection!.SendBizMsg(msg, 1);
                if (ret < 0) {
                    return new UfxErrorInfo(ret,
                        $"{UfxNames.GetFunctionName(functionId)}:{_ufxConnection.GetErrorMsg(ret)}");
                }
            }

            return UfxErrorInfo.Ok;
        }

        public UfxErrorInfo AsyncCall(IUfxFunctionCall function)
        {
            using var packer = function.Pack();
            return AsyncCall(function.FunctionId, packer);
        }

        public (CT2UnPacker? result, UfxErrorInfo error) SyncCall(IUfxFunctionCall function)
        {
            using var packer = function.Pack();
            return SyncCall(function.FunctionId, packer);
        }

        private (CT2UnPacker, UfxErrorInfo) CheckResult(int functionId, int ret, string err, CT2UnPacker result)
        {
            var error = UfxErrorInfo.Ok;
            /*
             *  如果ret等于0，表示业务数据接收成功，并且业务操作成功，unReturnPacker有对应的应答数据，lppStr为null。
             *  如果ret等于1，表示业务数据接收成功，但业务操作失败了，unReturnPacker有对应的应答数据，lppStr为null。
             *  如果ret等于2，表示收到非业务错误信息，lppStr为具体错误信息，但是lppUnPacker为null。
             *  如果ret等于3，表示业务包解包失败。lppStr为null，unReturnPacker为null。
             *  该方法的最后一个参数表示接收选项，接收超时之后是否删除对应发送句柄的数据。
             * */
            switch (ret) {
                case 0:
                case 1:
                    error = result.ReadErrorInfo(functionId);
                    //result.SetCurrentDatasetByIndex(1);
                    break;
                case 2:
                    error = new UfxErrorInfo(ret, $"{UfxNames.GetFunctionName(functionId)}:{err}");
                    break;
                case 3:
                    error = new UfxErrorInfo(ret, $"{UfxNames.GetFunctionName(functionId)}:{_ufxConnection!.GetErrorMsg(ret)}");
                    break;
            }
            return (result, error);
        }

        public (CT2UnPacker?, UfxErrorInfo) SyncCall(int functionId, CT2Packer packer)
        {
            var ret = _ufxConnection!.SendBiz(functionId, packer, 0, 0, 1);
            if (ret > 0) {
                ret = _ufxConnection.RecvBiz(ret, out string err, out CT2UnPacker result, 5000, 0);
                return CheckResult(functionId, ret, err, result);
            }
            var error = new UfxErrorInfo(
                ret,
                $"{UfxNames.GetFunctionName(functionId)}:{_ufxConnection.GetErrorMsg(ret)}");
            return (null, error);
        }

        public (UfxAccountInfo, UfxErrorInfo) LoadAccountInfo()
        {
            var error = UfxErrorInfo.Ok;
            var info = new UfxAccountInfo { UserToken = _userToken };
            using var config = GetClientConfig();
            info.AccountCode = config.GetString("account", "account_code", string.Empty);
            info.AssetNo = config.GetString("account", "asset_code", string.Empty);
            info.CombinationNo = config.GetString("account", "combination_code", string.Empty);
            info.StockholderId = config.GetString("account", "stockholder_id", string.Empty);
            if (string.IsNullOrEmpty(info.AssetNo)) {
                error = GetAccountInfo(info);
            }
            return (info, error);
        }

        private UfxErrorInfo GetAccountInfo(UfxAccountInfo info)
        {
            var (result, error) = SyncCall(UfxAccountFunction.Make30002(_userToken));
            if (error == UfxErrorInfo.Ok) {
                var data = Ufx30002Result.ReadOne(result);
                info.AccountCode = data.AccountCode;
                info.AssetNo = data.AssetNo;
            }
            return error;
        }

        public void Open(string settingFile)
        {
            if (_task != null) {
                return;
            }

            _settingFile = settingFile;
            if (!File.Exists(_settingFile)) {
                throw new FileNotFoundException($"找不到配置文件: {_settingFile}");
            }
            _settingPath = Path.GetDirectoryName(_settingFile)!;
            _task = LongRunningTask.StartNew(Run);
            _task.Start();
        }

        public void Close()
        {
            _task?.Stop();
            _task = null;
        }
        private static CT2Packer MakeSubscribePack(string operatorNo, string password)
        {
            var packer = new CT2Packer(2);
            packer.BeginPack();
            packer.AddField("login_operator_no", (sbyte)'S', 16, 4);
            packer.AddField("password", (sbyte)'S', 16, 4);
            packer.AddStr(operatorNo);
            packer.AddStr(password);
            packer.EndPack();
            return packer;
        }
        public UfxErrorInfo Subscribe(string operatorNo, string password, CT2SubCallbackInterface callback)
        {
            var config = GetSubscribeConfig();
            _subscriber = _mcConnection!.NewSubscriber(
                callback,
                config.GetString("subscribe", "biz_name", string.Empty),
                _timeout,
                2000,
                100);
            if (_subscriber == null) {
                return new UfxErrorInfo(-1, "NewSubscriber 调用失败");
            }
            var request = MakeSubscribePack(operatorNo, password);
            var args = new CT2SubscribeParamInterface();
            args.SetTopicName(config.GetString("subscribe", "topic_name", "ufx_topic"));
            args.SetReplace(true);
            args.SetFilter("operator_no", operatorNo);
            var ret = _subscriber.SubscribeTopicEx(args, (uint)_timeout, out CT2UnPacker result, request);
            return ret > 0 ? UfxErrorInfo.Ok : new UfxErrorInfo(result.GetErrCode(), result.GetErrMsg());
        }
    }
}